﻿using System;
using HiTeachers.Utilities;

namespace HiTeachers.Entities
{
    /// <summary>
    /// For a discussion of this object, see 
    /// http://devlicio.us/blogs/billy_mccafferty/archive/2007/04/25/using-equals-gethashcode-effectively.aspx
    /// </summary>
    public abstract class DomainObject<TIdT> where TIdT : IComparable
    {
        #region Constructors

        protected DomainObject() { }

        protected DomainObject(TIdT id)
        {
            Id = id;
        }
        #endregion

        protected TIdT Id = default(TIdT);

        /// <summary>
        /// ID may be of type string, int, custom type, etc.
        /// Setter is protected to allow unit tests to set this property via reflection and to allow 
        /// domain objects more flexibility in setting this for those objects with assigned IDs.
        /// </summary>
        public virtual TIdT ID
        {
            get { return Id; }
            set { Id = value; }
        }

        //public override sealed bool Equals(object obj) {
        public new virtual bool Equals(object obj)
        {
            var compareTo = obj as DomainObject<TIdT>;

            return (compareTo != null) &&
                   (HasSameNonDefaultIdAs(compareTo) ||
                // Since the IDs aren't the same, either of them must be transient to 
                // compare business value signatures
                    (((IsTransient()) || compareTo.IsTransient()) &&
                     HasSameBusinessSignatureAs(compareTo)));
        }

        /// <summary>
        /// Transient objects are not associated with an item already in storage.  For instance,
        /// a <see cref="Customer" /> is transient if its ID is 0.
        /// </summary>
        public virtual bool IsTransient()
        {
            return ID == null || ID.Equals(default(TIdT));
        }

        /// <summary>
        /// Must be provided to properly compare two objects
        /// </summary>
        public abstract override int GetHashCode();

        private bool HasSameBusinessSignatureAs(DomainObject<TIdT> compareTo)
        {
            Check.Require(compareTo != null, "compareTo may not be null");

            return GetHashCode().Equals(compareTo.GetHashCode());
        }

        /// <summary>
        /// Returns true if self and the provided persistent object have the same ID values 
        /// and the IDs are not of the default ID value
        /// </summary>
        private bool HasSameNonDefaultIdAs(DomainObject<TIdT> compareTo)
        {
            Check.Require(compareTo != null, "compareTo may not be null");

            return (ID != null && !ID.Equals(default(TIdT))) &&
                   (compareTo.ID != null && !compareTo.ID.Equals(default(TIdT))) &&
                   ID.Equals(compareTo.ID);
        }
    }
}
